﻿using OpenCvSharp;
using RemoteSensingImageDirectServer.Models.Map.Render;
using RemoteSensingImageDirectServer.Models.Tile;
using static System.Collections.Specialized.BitVector32;

namespace RemoteSensingImageDirectServer.Untils.Extensions
{
    public static class ArrayExtensions
    {
        /// <summary>
        /// 将byte类型的数组按位置填充到目标数组中去
        /// </summary>
        /// <param name="arr">当前数据的数组</param>
        /// <param name="tileRange">填充范围</param>
        /// <param name="imageXSize">目标数组的宽</param>
        /// <param name="imageYSize">目标数组的高</param>
        /// <param name="min">当前数组转换填充时的最小值限制</param>
        /// <param name="max">当前数组转换填充时的最大值限制</param>
        /// <param name="nodata">当前数组转换填充时的无效值限制</param>
        /// <param name="backColor">当前数组转换填充时的背景值限制</param>
        /// <returns>返回一个已经填充好值的OpenCV的单波段的mat对象</returns>
        public static Mat FillArray(this byte[] arr, TileRange tileRange, int imageXSize, int imageYSize, 
            int min = 0, int max = 255, double nodata = 0, byte backColor = 0)
        {
            var mArr = Enumerable.Repeat(backColor, imageXSize * imageYSize).ToArray();

            if (min == 0 && max == 255)
            {
                for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
                {
                    for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                    {
                        var val = arr[index++];
                        if (val == nodata)
                            mArr[x + y * imageXSize] = 0;
                        else
                            mArr[x + y * imageXSize] = val;
                    }
                }
                
                return new Mat(imageYSize, imageXSize, MatType.CV_8UC1, mArr);
            }

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];

                    if (val < min)
                    {
                        if (val == nodata)
                            mArr[x + y * imageXSize] = 0;
                        else
                            mArr[x + y * imageXSize] = 1;
                    }
                    else if (val > max)
                    {
                        mArr[x + y * imageXSize] = 254;
                    }
                    else
                    {
                        mArr[x + y * imageXSize] = (byte)(255.0 * ((double)val - min) / (max - min));
                    }
                }
            }

            return new Mat(imageYSize, imageXSize, MatType.CV_8UC1, mArr);
        }

        /// <summary>
        /// 将int类型的数组按位置填充到目标数组中去
        /// </summary>
        /// <param name="arr">当前数据的数组</param>
        /// <param name="tileRange">填充范围</param>
        /// <param name="imageXSize">目标数组的宽</param>
        /// <param name="imageYSize">目标数组的高</param>
        /// <param name="min">当前数组转换填充时的最小值限制</param>
        /// <param name="max">当前数组转换填充时的最大值限制</param>
        /// <param name="nodata">当前数组转换填充时的无效值限制</param>
        /// <param name="backColor">当前数组转换填充时的背景值限制</param>
        /// <returns>返回一个已经填充好值的OpenCV的单波段的mat对象</returns>
        public static Mat FillArray(this int[] arr, TileRange tileRange, int imageXSize, int imageYSize, 
            int min = 0, int max = 255, double nodata = 0, byte backColor = 0)
        {
            var mArr = Enumerable.Repeat(backColor, imageXSize * imageYSize).ToArray();

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];

                    if (val < min)
                    {
                        if (val == nodata)
                            mArr[x + y * imageXSize] = 0;
                        else
                            mArr[x + y * imageXSize] = 1;
                    }
                    else if (val > max)
                    {
                        mArr[x + y * imageXSize] = 254;
                    }
                    else
                    {
                        mArr[x + y * imageXSize] = (byte)(255.0 * ((double)val - min) / (max - min));
                    }
                }
            }

            return new Mat(imageYSize, imageXSize, MatType.CV_8UC1, mArr); 
        }

        /// <summary>
        /// 将int类型的数组按位置填充到目标数组中去
        /// </summary>
        /// <param name="arr">当前数据的数组</param>
        /// <param name="tileRange">填充范围</param>
        /// <param name="imageXSize">目标数组的宽</param>
        /// <param name="imageYSize">目标数组的高</param>
        /// <param name="min">当前数组转换填充时的最小值限制</param>
        /// <param name="max">当前数组转换填充时的最大值限制</param>
        /// <param name="nodata">当前数组转换填充时的无效值限制</param>
        /// <param name="backColor">当前数组转换填充时的背景值限制</param>
        /// <returns>返回一个已经填充好值的OpenCV的单波段的mat对象</returns>
        public static Mat FillArray(this short[] arr, TileRange tileRange, int imageXSize, int imageYSize, 
            int min = 0, int max = 1024, double nodata = 0, byte backColor = 0)
        {
            var mArr = Enumerable.Repeat(backColor, imageXSize * imageYSize).ToArray();

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (int x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];

                    if (val <= min)
                    {
                        if (val == nodata)
                            mArr[x + y * imageXSize] = 0;
                        else
                            mArr[x + y * imageXSize] = 1;
                    }
                    else if (val > max)
                    {
                        mArr[x + y * imageXSize] = 254;
                    }
                    else
                    {
                        mArr[x + y * imageXSize] = (byte)((val - min) / (max - min) * 254.0 + 1);
                    }
                }
            }

            return new Mat(imageYSize, imageXSize, MatType.CV_8UC1, mArr);
        }

        /// <summary>
        /// 将浮点型数据填充到目标数组中去，并进行线性连续渲染
        /// </summary>
        /// <param name="arr">当前数据的数组</param>
        /// <param name="tileRange">填充范围</param>
        /// <param name="imageXSize">目标数组的宽</param>
        /// <param name="imageYSize">目标数组的高</param>
        /// <param name="backColorArray">当前数组转换填充时的背景值限制</param>
        /// <param name="min">当前数据的最小值</param>
        /// <param name="max">当前数据的最大值</param>
        /// <param name="nodata">当前数据的无效值</param>
        /// <returns>返回一个已经填充好值的OpenCV的mat对象</returns>
        public static Mat FillArray(this float[] arr, TileRange tileRange, int imageXSize, int imageYSize, byte[] backColorArray, 
            float min = 0, float max = 255, double nodata = 0)
        {
            //b,g,r,a
            var pMat = new Mat(imageYSize, imageXSize, MatType.CV_8UC4, new Scalar(backColorArray[0], backColorArray[1], backColorArray[2], 255));

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];
                    var matVal = pMat.Get<Vec4b>(y, x);
                    if (float.IsNaN(val) || val.Equals(nodata) || val.Equals(-32767))
                    {
                        matVal.Item3 = 0;
                    }
                    else if (val <= min)
                    {
                        matVal.Item0 = 0;
                        matVal.Item1 = 0;
                        matVal.Item2 = 0;
                    }
                    else if (val >= max)
                    {
                        matVal.Item0 = 255;
                        matVal.Item1 = 255;
                        matVal.Item2 = 255;
                    }
                    else
                    {
                        var rval = (val - min) / (max - min);
                        matVal.Item0 = (byte)(255 * rval);
                        matVal.Item1 = (byte)(255 * rval);
                        matVal.Item2 = (byte)(255 * rval);
                    }

                    pMat.Set(y, x, matVal);
                }
            }
            return pMat;
        }


        public static Mat FillArray(this float[] arr, TileRange tileRange, MapRender render, int imageXSize, int imageYSize, byte[] backColorArray, 
            float min = 0, float max = 255, double nodata = 0)
        {
            //b,g,r,a
            var pMat = new Mat(imageYSize, imageXSize, MatType.CV_8UC4, new Scalar(backColorArray[0], backColorArray[1], backColorArray[2], 255));

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];
                    var matVal = pMat.Get<Vec4b>(y, x);
                    if (float.IsNaN(val) || val.Equals(nodata) || val.Equals(-32767))
                    {
                        matVal.Item3 = 0;
                    }
                    else
                    {
                        if (render.RenderType == 1)
                        {
                            var linearRender = render.LinearRender;

                            if (val < linearRender.StartValue || val > linearRender.EndValue)
                            {
                                matVal.Item3 = 0;
                            }
                            else
                            {
                                var rval = (val - linearRender.StartValue) / (linearRender.EndValue - linearRender.StartValue);
                                matVal.Item0 = (byte)(linearRender.StColor.B + (linearRender.EnColor.B - linearRender.StColor.B) * rval);
                                matVal.Item1 = (byte)(linearRender.StColor.G + (linearRender.EnColor.G - linearRender.StColor.G) * rval);
                                matVal.Item2 = (byte)(linearRender.StColor.R + (linearRender.EnColor.R - linearRender.StColor.R) * rval);
                                matVal.Item3 = (byte)(linearRender.StColor.A + (linearRender.EnColor.A - linearRender.StColor.A) * rval);
                            }
                        }
                        else if (render.RenderType == 2)
                        {
                            var section = render.SegmentedRender.Sections.SingleOrDefault(q => q.Min <= val && val <= q.Max);
                            if (section == null)
                            {
                                matVal.Item3 = 0;
                            }
                            else
                            {
                                matVal.Item0 = section.DColor.B;
                                matVal.Item1 = section.DColor.G;
                                matVal.Item2 = section.DColor.R;
                                matVal.Item3 = section.DColor.A;
                            }
                        }
                        else if (render.RenderType == 3)
                        {
                            var sectionIndex = render.SegmentedRender.Sections.FindIndex(q => q.Min <= val && val <= q.Max);
                            if (sectionIndex == -1)
                            {
                                matVal.Item3 = 0;
                            }
                            else if (sectionIndex < render.SegmentedRender.Sections.Count - 1)
                            {
                                var stSection = render.SegmentedRender.Sections[sectionIndex];
                                var enSection = render.SegmentedRender.Sections[sectionIndex + 1];

                                var rval = (val - stSection.Min) / (stSection.Max - stSection.Min);
                                matVal.Item0 = (byte)(stSection.DColor.B + (enSection.DColor.B - stSection.DColor.B) * rval);
                                matVal.Item1 = (byte)(stSection.DColor.G + (enSection.DColor.G - stSection.DColor.G) * rval);
                                matVal.Item2 = (byte)(stSection.DColor.R + (enSection.DColor.R - stSection.DColor.R) * rval);
                                matVal.Item3 = (byte)(stSection.DColor.A + (enSection.DColor.A - stSection.DColor.A) * rval);
                            }
                            else
                            {
                                var stSection = render.SegmentedRender.Sections[sectionIndex];
                                matVal.Item0 = stSection.DColor.B;
                                matVal.Item1 = stSection.DColor.G;
                                matVal.Item2 = stSection.DColor.R;
                                matVal.Item3 = stSection.DColor.A;
                            }
                        }
                    }
                    pMat.Set(y, x, matVal);
                }
            }
            return pMat;
        }


        public static Mat FillArray(this double[] arr, TileRange tileRange, int imageXSize, int imageYSize, byte[] backColorArray, 
            float min = 0, float max = 255, double nodata = 0)
        {
            //b,g,r,a
            var pMat = new Mat(imageYSize, imageXSize, MatType.CV_8UC4, new Scalar(backColorArray[0], backColorArray[1], backColorArray[2], 255));

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];
                    var matVal = pMat.Get<Vec4b>(y, x);
                    if (double.IsNaN(val) || val.Equals(nodata) || val.Equals(-32767))
                    {
                        matVal.Item3 = 0;
                    }
                    else if (val <= min)
                    {
                        matVal.Item0 = 0;
                        matVal.Item1 = 0;
                        matVal.Item2 = 0;
                    }
                    else if (val >= max)
                    {
                        matVal.Item0 = 255;
                        matVal.Item1 = 255;
                        matVal.Item2 = 255;
                    }
                    else
                    {
                        var rval = (val - min) / (max - min);
                        matVal.Item0 = (byte)(255 * rval);
                        matVal.Item1 = (byte)(255 * rval);
                        matVal.Item2 = (byte)(255 * rval);
                    }

                    pMat.Set(y, x, matVal);
                }
            }
            return pMat;
        }


        public static Mat FillArray(this double[] arr, TileRange tileRange, MapRender render, int imageXSize, int imageYSize, byte[] backColorArray, 
            float min = 0, float max = 255, double nodata = 0)
        {
            //b,g,r,a
            var pMat = new Mat(imageYSize, imageXSize, MatType.CV_8UC4, new Scalar(backColorArray[0], backColorArray[1], backColorArray[2], 255));

            for (int index = 0, y = tileRange.StartRow; y < tileRange.EndRow; y++)
            {
                for (var x = tileRange.StartCol; x < tileRange.EndCol; x++)
                {
                    var val = arr[index++];
                    var matVal = pMat.Get<Vec4b>(y, x);
                    if (double.IsNaN(val) || val.Equals(nodata) || val.Equals(-32767))
                    {
                        matVal.Item3 = 0;
                    }
                    else
                    {
                        if (render.RenderType == 1)
                        {
                            var linearRender = render.LinearRender;

                            if (val < linearRender.StartValue || val > linearRender.EndValue)
                            {
                                matVal.Item3 = 0;
                            }
                            else
                            {
                                var rval = (val - linearRender.StartValue) / (linearRender.EndValue - linearRender.StartValue);
                                matVal.Item0 = (byte)(linearRender.StColor.B + (linearRender.EnColor.B - linearRender.StColor.B) * rval);
                                matVal.Item1 = (byte)(linearRender.StColor.G + (linearRender.EnColor.G - linearRender.StColor.G) * rval);
                                matVal.Item2 = (byte)(linearRender.StColor.R + (linearRender.EnColor.R - linearRender.StColor.R) * rval);
                                matVal.Item3 = (byte)(linearRender.StColor.A + (linearRender.EnColor.A - linearRender.StColor.A) * rval);
                            }
                        }
                        else if (render.RenderType == 2)
                        {
                            var section = render.SegmentedRender.Sections.SingleOrDefault(q => q.Min <= val && val <= q.Max);
                            if (section == null)
                            {
                                matVal.Item3 = 0;
                            }
                            else
                            {
                                matVal.Item0 = section.DColor.B;
                                matVal.Item1 = section.DColor.G;
                                matVal.Item2 = section.DColor.R;
                                matVal.Item3 = section.DColor.A;
                            }
                        }
                        else if (render.RenderType == 3)
                        {
                            var sectionIndex = render.SegmentedRender.Sections.FindIndex(q => q.Min <= val && val <= q.Max);
                            if (sectionIndex == -1)
                            {
                                matVal.Item3 = 0;
                            }
                            else if (sectionIndex < render.SegmentedRender.Sections.Count - 1)
                            {
                                var stSection = render.SegmentedRender.Sections[sectionIndex];
                                var enSection = render.SegmentedRender.Sections[sectionIndex + 1];

                                var rval = (val - stSection.Min) / (stSection.Max - stSection.Min);
                                matVal.Item0 = (byte)(stSection.DColor.B + (enSection.DColor.B - stSection.DColor.B) * rval);
                                matVal.Item1 = (byte)(stSection.DColor.G + (enSection.DColor.G - stSection.DColor.G) * rval);
                                matVal.Item2 = (byte)(stSection.DColor.R + (enSection.DColor.R - stSection.DColor.R) * rval);
                                matVal.Item3 = (byte)(stSection.DColor.A + (enSection.DColor.A - stSection.DColor.A) * rval);
                            }
                            else
                            {
                                var stSection = render.SegmentedRender.Sections[sectionIndex];
                                matVal.Item0 = stSection.DColor.B;
                                matVal.Item1 = stSection.DColor.G;
                                matVal.Item2 = stSection.DColor.R;
                                matVal.Item3 = stSection.DColor.A;
                            }
                        }
                    }
                    pMat.Set(y, x, matVal);
                }
            }
            return pMat;
        }
    }
}
